package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.*;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.model.po.LearningLesson;
import com.tianji.learning.model.po.LearningRecord;
import com.tianji.learning.model.vo.LearningLessonVO;
import com.tianji.learning.model.vo.LearningPlanPageVO;
import com.tianji.learning.model.vo.LearningPlanVO;
import com.tianji.learning.model.vo.LessonStatusVO;
import com.tianji.learning.service.ILearningLessonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 学生课表 服务实现类
 * </p>
 *
 * @author AvA
 * @since 2024-12-06
 */
@Service
@Slf4j
@SuppressWarnings("ALL")
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;

    private final CatalogueClient catalogueClient;

    private final LearningRecordMapper recordMapper;

    /**
     * 添加课程信息到用户课程表
     *
     * @param userId
     * @param courseIds
     */
    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {

        //查询课程有效期
        List<CourseSimpleInfoDTO> simpleInfoList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(simpleInfoList)) {
            log.error("课程信息不存在, 无法添加到课程表");
            return;
        }

        //循环遍历, 处理LearningLesson数据
        List<LearningLesson> list = new ArrayList<>(simpleInfoList.size());
        for (CourseSimpleInfoDTO coursesimpleInfo : simpleInfoList) {
            LearningLesson lesson = new LearningLesson();

            //获取过期时间
            Integer validDuration = coursesimpleInfo.getValidDuration();
            if (validDuration != null && validDuration > 0) {
                LocalDateTime now = LocalDateTime.now();
                lesson.setCreateTime(now);
                lesson.setExpireTime(now.plusMonths(validDuration));
            }

            //Lesson字段填充
            lesson.setUserId(userId);
            lesson.setCourseId(coursesimpleInfo.getId());
            list.add(lesson);
        }

        //批量新增
        this.saveBatch(list);
    }

    /**
     * 查询我的课表
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {

        //获取当前登录用户
        Long userId = UserContext.getUser();

        //分页查询
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(query.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = page.getRecords();

        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }

        //查询课程信息
        Map<Long, CourseSimpleInfoDTO> courseMap = queryCourseSimpleInfoList(records);

        //封装VO返回
        List<LearningLessonVO> list = new ArrayList<>(records.size());

        //循环遍历,将LearnningLesson转为VO
        for (LearningLesson record : records) {
            //属性拷贝
            LearningLessonVO learningLessonVO = BeanUtils.copyBean(record, LearningLessonVO.class);
            //属性填充
            CourseSimpleInfoDTO courseInfoDTO = courseMap.get(record.getCourseId());
            learningLessonVO.setCourseName(courseInfoDTO.getName());
            learningLessonVO.setCourseCoverUrl(courseInfoDTO.getCoverUrl());
            learningLessonVO.setSections(courseInfoDTO.getSectionNum());

            list.add(learningLessonVO);
        }

        return PageDTO.of(page, list);
    }

    /**
     * 查询课程简要信息列表
     * 本方法通过课程学习记录来查询对应的课程简要信息，并以Map形式返回
     *
     * @param records 学习课程记录列表，用于提取课程ID
     * @return 返回一个Map，键为课程ID，值为课程简要信息DTO
     * @throws BadRequestException 如果查询不到课程信息，则抛出异常
     */
    private Map<Long, CourseSimpleInfoDTO> queryCourseSimpleInfoList(List<LearningLesson> records) {

        //获取课程id
        Set<Long> cIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());

        //查询课程信息
        List<CourseSimpleInfoDTO> courseInfoList = courseClient.getSimpleInfoList(cIds);
        if (CollUtils.isEmpty(courseInfoList)) {
            //课程不存在,无法添加
            throw new BadRequestException("课程信息不存在!");
        }

        //把课程集合处理成Map，key是courseId，值是course本身
        Map<Long, CourseSimpleInfoDTO> courseMap = courseInfoList.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
        return courseMap;
    }

    /**
     * 查询当前用户正在学习的课程信息
     * <p>
     * 此方法通过查询数据库来获取当前用户最新学习的课程，并将其详细信息返回
     * 如果用户没有正在学习的课程，则返回null
     *
     * @return LearningLessonVO 当前用户正在学习的课程视图对象，如果没有则返回null
     */
    @Override
    public LearningLessonVO queryMyCurrentLesson() {
        // 获取当前用户ID
        Long userId = UserContext.getUser();

        // 查询用户正在学习的最新课程记录
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();

        // 如果没有找到正在学习的课程，返回null
        if (ObjectUtils.isEmpty(lesson)) {
            return null;
        }

        // 将课程记录转换为视图对象
        LearningLessonVO learningLessonVO = BeanUtils.copyBean(lesson, LearningLessonVO.class);

        // 调用课程服务获取课程详细信息
        CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        // 如果课程不存在，抛出异常
        if (ObjectUtils.isEmpty(courseInfo)) {
            throw new BadRequestException("课程不存在");
        }

        // 设置课程名称、封面URL和章节数量到视图对象
        learningLessonVO.setCourseName(courseInfo.getName());
        learningLessonVO.setCourseCoverUrl(courseInfo.getCoverUrl());
        learningLessonVO.setSections(courseInfo.getSectionNum());

        // 计算用户学习的课程总数
        Integer courseAmount = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .count();

        // 设置课程总数到视图对象
        learningLessonVO.setCourseAmount(courseAmount);

        // 批量查询目录信息，此处只查询最新的章节信息
        List<CataSimpleInfoDTO> cataInfos = catalogueClient.batchQueryCatalogue(CollUtils.singletonList(lesson.getLatestSectionId()));

        // 如果查询到目录信息，设置最新章节名称和索引到视图对象
        if (!CollUtils.isEmpty(cataInfos)) {
            CataSimpleInfoDTO cataInfo = cataInfos.get(0);
            learningLessonVO.setLatestSectionName(cataInfo.getName());
            learningLessonVO.setLatestSectionIndex(cataInfo.getCIndex());
        }

        // 返回视图对象
        return learningLessonVO;
    }

    /**
     * 根据用户ID和课程ID列表移除用户的学习课程记录
     *
     * @param userId    用户ID，用于定位用户的学习课程记录
     * @param courseIds 课程ID列表，用于指定需要移除的学习课程范围
     */
    @Override
    public void removeUserLessons(Long userId, List<Long> courseIds) {

        // 查询用户指定课程的学习记录
        List<LearningLesson> learningLessonList = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseIds)
                .list();

        // 提取学习记录的ID
        List<Long> learningLessonIdList = learningLessonList.stream().map(LearningLesson::getId).collect(Collectors.toList());

        // 根据ID列表移除学习记录
        this.removeByIds(learningLessonIdList);
    }

    /**
     * 检查课程是否有效
     * <p>
     * 通过查询学习课程记录来判断指定的课程ID对应的课程对于当前用户是否有效课程有效是指课程状态不是过期
     *
     * @param courseId 课程ID，用于查询学习课程记录
     * @return 如果找到有效的学习课程记录，则返回该记录的ID；否则返回null
     */
    @Override
    public Long isLessonValid(Long courseId) {

        // 获取当前用户ID
        Long userId = UserContext.getUser();

        // 查询学习课程记录，条件是用户ID和课程ID匹配，且课程状态不是过期
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .ne(LearningLesson::getStatus, LessonStatus.EXPIRED)
                .one();

        // 如果查询到有效的学习课程记录，则返回该记录的ID
        if (ObjectUtils.isNotEmpty(learningLesson)) {
            return learningLesson.getId();
        }

        // 如果没有查询到有效的学习课程记录，则返回null
        return null;
    }

    /**
     * 查询用户课表中指定课程状态
     *
     * @param courseId
     * @return
     */
    @Override
    public LessonStatusVO getLessonStatusByCourseId(Long courseId) {

        Long userId = UserContext.getUser();

        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .ne(LearningLesson::getStatus, LessonStatus.EXPIRED)
                .one();

        LessonStatusVO lessonStatusVO = BeanUtils.copyBean(lesson, LessonStatusVO.class);

        return lessonStatusVO;
    }

    /**
     * 统计每个课程的报名人数
     * <p>
     * 此方法通过查询数据库中与给定课程ID关联的学习中的课程记录数来实现统计功能
     * 使用Lambda表达式进行查询，确保查询条件精确匹配课程ID
     *
     * @param courseId 课程ID，用于查询特定课程的学习课时数量
     * @return 学习中的课程数量如果找不到匹配的记录，则返回0
     */
    @Override
    public Integer countLearningLessonByCourse(Long courseId) {

        // 查询与给定课程ID匹配的条数
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .count();

        // 返回查询到的报名人数
        return count;
    }

    @Override
    public LearningLesson queryByUserAndCourseId(Long userId, Long courseId) {
        return getOne(buildUserIdAndCourseIdWrapper(userId, courseId));
    }

    private LambdaQueryWrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {

        LambdaQueryWrapper<LearningLesson> queryWrapper = new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
        return queryWrapper;
    }

    @Override
    public void createLearningPlans(Long courseId, Integer freq) {
        // 1.获取当前登录的用户
        Long userId = UserContext.getUser();
        // 2.查询课表中的指定课程有关的数据
        LearningLesson lesson = queryByUserAndCourseId(userId, courseId);
        AssertUtils.isNotNull(lesson, "课程信息不存在！");
        // 3.修改数据
        LearningLesson l = new LearningLesson();
        l.setId(lesson.getId());
        l.setWeekFreq(freq);
        if(lesson.getPlanStatus() == PlanStatus.NO_PLAN) {
            l.setPlanStatus(PlanStatus.PLAN_RUNNING);
        }
        updateById(l);
    }



    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery query) {
        LearningPlanPageVO planPageVO = new LearningPlanPageVO();
        //1.获取用户
        Long user = UserContext.getUser();
        //2.todo 积分奖励
        planPageVO.setWeekPoints(0);
        //3.查询课程表信息
        //3.1获取本周内时间
        LocalDate now = LocalDate.now();
        LocalDateTime begin = DateUtils.getWeekBeginTime(now);
        LocalDateTime end = DateUtils.getWeekEndTime(now);
        //3.2获取本周已完成小节总数
        Integer weekFinished = recordMapper.selectCount(new LambdaQueryWrapper<LearningRecord>()
                .eq(LearningRecord::getUserId, user)
                .eq(LearningRecord::getFinished, true)
                .gt(LearningRecord::getFinishTime, begin)
                .lt(LearningRecord::getFinishTime, end)
        );
        planPageVO.setWeekFinished(weekFinished);

        //3.3获取用户所有计划小节数量
        QueryWrapper<LearningLesson> wrapper = new QueryWrapper<>();
        wrapper.select("SUM(week_freq) AS plansTotal");
        wrapper.eq("user_id", user);
        wrapper.in("status", LessonStatus.NOT_BEGIN, LessonStatus.LEARNING);
        wrapper.eq("plan_status", PlanStatus.PLAN_RUNNING);
        Map<String, Object> map = getMap(wrapper);
        if(map != null && map.get("plansTotal") != null){
            //得到结果
            Object plansTotal = map.get("plansTotal");
            //SUM类型默认为BigDecimal，所以需要转换为int
            Integer plans = Integer.valueOf(plansTotal.toString());
            planPageVO.setWeekTotalPlan(plans);
        }else{
            planPageVO.setWeekTotalPlan(0);
        }
        //3.4查询所有有计划的课程集合
        Page<LearningLesson> lessonPage = lambdaQuery()
                .eq(LearningLesson::getUserId, user)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .page(query.toMpPage("latest_learn_time", false));
        //如果没有计划的课程返回一个空分页
        List<LearningLesson> records = lessonPage.getRecords();
        if(CollUtils.isEmpty(records)){
            planPageVO.setTotal(0L);
            planPageVO.setPages(0L);
            planPageVO.setList(CollUtils.emptyList());
            return planPageVO;
        }

        //4.查询课程相关信息
        //查询每一门课程中的：本周已学小节、本周计划小节、小节已学数量、小节总数量、课程名字
        //4.1远程批量查询课程集合
        Map<Long, CourseSimpleInfoDTO> cinfos = queryCourseSimpleInfoList(records);
        //4.2远程批量查询各门课程本周已学小节
        //这里我使用先查出已学小节的集合，然后遍历集合输出map<课表id, 已学小节数量>
        //也可以在mapper写sql查出，做法在在线文档就已经实现
        Map<Long, Long> weekLearnedSections = recordMapper.selectList(
                new LambdaQueryWrapper<LearningRecord>()
                        .eq(LearningRecord::getUserId, 2)
                        .eq(LearningRecord::getFinished, 1)
                        .gt(LearningRecord::getFinishTime, begin)
                        .lt(LearningRecord::getFinishTime, end)
                //对课表分组就是对课程分组，groupingBy分组时调用counting函数即可对分组中的数量进行统计，统计的结果就是已学小节数
        ).stream().collect(Collectors.groupingBy(LearningRecord::getLessonId, Collectors.counting()));


        //4.2封装每个课程
        List<LearningPlanVO> planVOS = records.stream().map(lesson -> {
            LearningPlanVO learningPlanVO = BeanUtils.copyBean(lesson, LearningPlanVO.class);
            //4.3设置该课程的信息(本来这里写的是远程获取，但是已经有了批量获取课程的方法了，为了提高效率所以这里使用map来直接获取)
            CourseSimpleInfoDTO cinfo = cinfos.get(lesson.getCourseId());
            //小节总数量
            learningPlanVO.setSections(cinfo.getSectionNum());
            //课程名字
            learningPlanVO.setCourseName(cinfo.getName());
            //4.4设置该课程本周已学小节
            Long sections = weekLearnedSections.get(lesson.getId());
            //这里一定要判断一下，因为如果该课程用户本周并没有学习小节时，map就没有该记录
            //所以要给一个默认值0
            if(sections == null){
                learningPlanVO.setWeekLearnedSections(0);
            }else{
                learningPlanVO.setWeekLearnedSections(Integer.valueOf(sections.toString()));
            }
            return learningPlanVO;
        }).collect(Collectors.toList());

        //5.封装pageVo
        return planPageVO.pageInfo(lessonPage.getTotal(), lessonPage.getPages(), planVOS);
    }
}